package org.quincy.rock.core.dao.sql;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import org.quincy.rock.core.dao.NameInterceptor;
import org.quincy.rock.core.dao.ValueInterceptor;
import org.quincy.rock.core.dao.sql.Sort.Order;
import org.quincy.rock.core.exception.IllegalIdentifierException;
import org.quincy.rock.core.util.CoreUtil;
import org.quincy.rock.core.util.StringUtil;

/**
 * <b>Sort。</b>
 * <p><b>详细说明：</b></p>
 * <!-- 在此添加详细说明 -->
 * 无。
 * @version 1.0
 * @author mex2000
 * @since 1.0
 */
public class Sort extends AbstractExpression implements Iterable<Order> {

	/**
	 * serialVersionUID。
	 */
	private static final long serialVersionUID = -251462280817960964L;

	public static final Direction DEFAULT_DIRECTION = Direction.ASC;

	private static final Sort UNSORTED = Sort.by(new Order[0]);

	private final List<Order> orders;

	private Sort(List<Order> orders) {
		this.orders = orders;
	}

	private Sort(Direction direction, List<String> properties) {
		if (CoreUtil.isEmpty(properties))
			throw new IllegalArgumentException("You have to provide at least one property to sort by!");

		List<Order> list = new ArrayList<>();
		for (String property : properties) {
			list.add(new Order(property, direction));
		}
		this.orders = list;
	}

	public static boolean isUnsorted(Sort sort) {
		return sort == null || !sort.isSorted();
	}

	public static boolean isSorted(Sort sort) {
		return !isUnsorted(sort);
	}

	public static Sort unsorted() {
		return UNSORTED;
	}

	public static Sort by(Order... orders) {
		Objects.requireNonNull(orders);
		return new Sort(Arrays.asList(orders));
	}

	public static Sort by(String... properties) {
		Objects.requireNonNull(properties);
		return properties.length == 0 ? Sort.unsorted() : new Sort(DEFAULT_DIRECTION, Arrays.asList(properties));
	}

	public static Sort by(List<Order> orders) {
		Objects.requireNonNull(orders);
		return orders.isEmpty() ? Sort.unsorted() : new Sort(orders);
	}

	public static Sort by(Direction direction, String... properties) {
		Objects.requireNonNull(direction);
		Objects.requireNonNull(properties);
		return new Sort(direction, Arrays.asList(properties));
	}

	/**
	 * <b>解析排序字符串。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param _sort 排序字符串
	 * @return Sort
	 */
	public static Sort parse(String _sort) {
		if (StringUtil.isEmpty(_sort))
			return Sort.unsorted();
		final List<Order> list = new ArrayList<>();
		StringUtil.split(_sort, ',', (index, ele) -> list.add(Order.parse(ele.toString())));
		return list.isEmpty() ? Sort.unsorted() : Sort.by(list);
	}

	public boolean isSorted() {
		return !orders.isEmpty();
	}

	public boolean isUnsorted() {
		return !isSorted();
	}

	public Iterator<Order> iterator() {
		return this.orders.iterator();
	}

	private Sort withDirection(Direction direction) {
		int len = orders.size();
		Order[] list = new Order[len];
		for (int i = 0; i < len; i++) {
			list[i] = orders.get(i).with(direction);
		}
		return Sort.by(list);
	}

	public Sort descending() {
		return withDirection(Direction.DESC);
	}

	public Sort ascending() {
		return withDirection(Direction.ASC);
	}

	public Sort and(Sort sort) {
		Objects.requireNonNull(sort, "Sort must not be null!");
		ArrayList<Order> these = new ArrayList<>(this.orders);
		for (Order order : sort) {
			these.add(order);
		}
		return Sort.by(these);
	}

	@Override
	public StringBuilder build(StringBuilder buffer, NameInterceptor nameInterceptor,
			ValueInterceptor valueInterceptor) {
		for (int i = 0, len = orders.size(); i < len; i++) {
			if (i != 0)
				buffer.append(StringUtil.CHAR_COMMA);
			orders.get(i).build(buffer, nameInterceptor, valueInterceptor);
		}
		return buffer;
	}

	public CharSequence toCharSequence(NameInterceptor nameInterceptor) {
		return build(new StringBuilder(), nameInterceptor, null);
	}

	public String toString(NameInterceptor nameInterceptor) {
		return build(new StringBuilder(), nameInterceptor, null).toString();
	}

	public static enum Direction {

		ASC, DESC;

		public boolean isAscending() {
			return this.equals(ASC);
		}

		public boolean isDescending() {
			return this.equals(DESC);
		}

		public static Direction fromString(String value) {
			try {
				return Direction.valueOf(value.toUpperCase(Locale.US));
			} catch (Exception e) {
				throw new IllegalArgumentException(String.format(
						"Invalid value '%s' for orders given! Has to be either 'desc' or 'asc' (case insensitive).",
						value), e);
			}
		}
	}

	public static class Order extends AbstractExpression {
		/**
		 * serialVersionUID。
		 */
		private static final long serialVersionUID = 5356586462377365795L;

		private final String property;
		private final Direction direction;

		public Order(String property, Direction direction) {
			if (!StringUtil.isValidHierarchyIdentifier(property)) {
				throw new IllegalIdentifierException(property);
			}
			this.property = property;
			this.direction = direction;
		}

		public static Order by(String property) {
			return new Order(property, DEFAULT_DIRECTION);
		}

		public static Order asc(String property) {
			return new Order(property, Direction.ASC);
		}

		public static Order desc(String property) {
			return new Order(property, Direction.DESC);
		}

		/**
		 * <b>解析排序字符串。</b>
		 * <p><b>详细说明：</b></p>
		 * <!-- 在此添加详细说明 -->
		 * 无。
		 * @param _order 排序字符串
		 * @return Order
		 */
		public static Order parse(String _order) {
			_order = _order.trim();
			int pos = _order.indexOf(' ');
			if (pos > 0) {
				String property = _order.substring(0, pos).trim();
				Direction direction = Direction.fromString(_order.substring(pos + 1).trim());
				return new Order(property, direction);
			} else
				return new Order(_order.trim(), Direction.ASC);
		}

		public Direction getDirection() {
			return direction;
		}

		public String getProperty() {
			return property;
		}

		public String columnName(NameInterceptor nameInterceptor) {
			return buildName(new StringBuilder(), nameInterceptor).toString();
		}

		public String columnName() {
			return buildName(new StringBuilder(), nameInterceptor()).toString();
		}

		public boolean isAscending() {
			return this.direction.isAscending();
		}

		public boolean isDescending() {
			return this.direction.isDescending();
		}

		public Order with(Direction direction) {
			return new Order(this.property, direction);
		}

		public String toString(NameInterceptor nameInterceptor) {
			return buildName(new StringBuilder(), nameInterceptor).append(StringUtil.CHAR_SPACE)
					.append(direction.name()).toString();
		}

		public CharSequence toCharSequence(NameInterceptor nameInterceptor) {
			return buildName(new StringBuilder(), nameInterceptor).append(StringUtil.CHAR_SPACE)
					.append(direction.name());
		}

		@Override
		public StringBuilder build(StringBuilder buffer, NameInterceptor nameInterceptor,
				ValueInterceptor valueInterceptor) {
			return buildName(buffer, nameInterceptor).append(StringUtil.CHAR_SPACE).append(direction.name());
		}

		private StringBuilder buildName(StringBuilder buffer, NameInterceptor nameInterceptor) {
			if (nameInterceptor == null)
				buffer.append(property);
			else {
				int pos = property.lastIndexOf(StringUtil.CHAR_DOT);
				if (pos == -1) {
					buffer.append(nameInterceptor.applyColumnName(property));
				} else {
					buffer.append(
							nameInterceptor.applyColumnName(property.substring(0, pos), property.substring(pos + 1)));
				}
			}
			return buffer;
		}
	}

}
